package Operations;

import instrumentation.Condition;
import instrumentation.ConditionIf;
import instrumentation.Data;

import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.LinkedList;

import GA.ConstructorDeclared;
import GA.MethodDeclared;

import util.Filter;
import util.CanBeUsedAs;
import random.TestClusterBuilder;


public abstract class Operation   {
	// generation probabilities
    public static float WORK_ON_CUT = .28f;
    public static float SELECT_SUBCLASS = .35f;
    public static float GEN_NEW = 0.35f;

    public static float USE_CONSTANTS = 0.15f;
    public static float SET_TO_NULL = 0.05f;

    public static float GEN_BASIC_VALUES = .40f;
    public static float LIMITED_VALUES = .50f;


	@Override
	public abstract Operation clone();
	
	public static Operation randomlyGenerate(TestClusterBuilder TestCluster){
		GEN_NEW=.35f;
		Map<String, Class<?>> cluster= TestCluster.getCluster();
		Map<String, String[]>  refFactory=TestCluster.getRefFactory();
		Map<String, ConstructorDeclared[]> clazzConstructors= TestCluster.getClazzConstructors();
		Map<String, MethodDeclared[]> methodsToTest= TestCluster.getMethodsToTest();
		Set<Class<?>> classToTest= TestCluster.getClassToTest();
		
		while(true) {
			Operation op = null;
		try {

		//	List<String> clazzUTest = new ArrayList<String>(classToTest.keySet());   
			List<Class<?>> clazzUTest = new ArrayList<Class<?>>(new ArrayList<Class<?>>(classToTest));   
			Class<?> cut = clazzUTest.get(ValuesGenerator.getRandomInt(classToTest.size()));
			String cName=cut.getName();
	//				System.out.println("Operation.randomlyGenerate cName:" + cName);
			ConstructorDeclared constr= null;
				
				ConstructorDeclared[] cutConst=clazzConstructors.get(cName);
				if(cutConst.length!=0){//continue;
					 constr= cutConst[ValuesGenerator.getRandomInt(cutConst.length)];
				}
				if(ValuesGenerator.getBoleean(GEN_NEW)) {	
				if(ValuesGenerator.getBoleean(USE_CONSTANTS))
					op =CreateObject.generate(constr,TestCluster);			
				else{
						if(constr==null) continue;
						op = AssignConstant.generate(cut,cName,cutConst[0], refFactory);
				} 
				}else op = invokeMethod(cName, cluster, refFactory,methodsToTest);
				if(op != null) return op;
			} catch(Throwable e) {
				System.err.println( "Operation.randomlyGenerat: Cannot create a random element: " + e.getMessage());
			}
		}

	}
	public static Operation recommendedGenerate(TestClusterBuilder TestCluster,Map<Integer, Map<Class<?>, List<String>>> methodsRecommendPValues){
		GEN_NEW=.35f;
		Map<String, Class<?>> cluster= TestCluster.getCluster();
		Map<String, String[]>  refFactory=TestCluster.getRefFactory();
		Map<String, ConstructorDeclared[]> clazzConstructors= TestCluster.getClazzConstructors();
		Map<String, MethodDeclared[]> methodsToTest= TestCluster.getMethodsToTest();
		Set<Class<?>> classToTest= TestCluster.getClassToTest();
		
		Map<Class<?>, List<String>> recommendMethods = methodsRecommendPValues.get(1);
		List<Class<?>> recClassToTest= new ArrayList<Class<?>>(recommendMethods.keySet());
		
		while(true) {
			Operation op = null;
		try {

		//	List<String> clazzUTest = new ArrayList<String>(classToTest.keySet());   
			List<Class<?>> clazzUTest = new ArrayList<Class<?>>(new ArrayList<Class<?>>(recClassToTest));   
			Class<?> cut = clazzUTest.get(ValuesGenerator.getRandomInt(recClassToTest.size()));
			String cName=cut.getName();
	//				System.out.println("Operation.randomlyGenerate cName:" + cName);
			//Add the recommended methods to MethodToTest
			List<String> recMethodToTest= new LinkedList<String>();
			recMethodToTest.addAll(recommendMethods.get(cut));
			if(recMethodToTest.contains("<init>")){
				recMethodToTest.remove("<init>");
				GEN_NEW=.45f;
				if(recMethodToTest.size()==0)
					GEN_NEW=1.0f;
			}
			MethodDeclared[] usableMethods=methodsToTest.get(cut.getName());
//			if(usableMethods==null)
//				System.out.print("");
			Map<String, MethodDeclared[]> newMethodToTest=new LinkedHashMap<String, MethodDeclared[]>();
			List<MethodDeclared> mDelList=new LinkedList<MethodDeclared>();
			int indx=0;
			for(int i=0;i<usableMethods.length;i++){
				if(recMethodToTest.contains(usableMethods[i].getName()))
					mDelList.add(usableMethods[i]);						
			}
			MethodDeclared[] mDeclared = mDelList.toArray(new MethodDeclared[mDelList.size()]);
			newMethodToTest.put(cut.getName(), mDeclared);
			ConstructorDeclared constr= null;
			
				ConstructorDeclared[] cutConst=clazzConstructors.get(cName);
				if(cutConst.length!=0){//continue;
					 constr= cutConst[ValuesGenerator.getRandomInt(cutConst.length)];
				}
				if(ValuesGenerator.getBoleean(GEN_NEW)) {	
				if(ValuesGenerator.getBoleean(USE_CONSTANTS))
					op =CreateObject.generate(constr,TestCluster);			
				else{
						if(constr==null) continue;
						op = AssignConstant.generate(cut,cName,cutConst[0], refFactory);
				} 
				}else op = invokeMethod(cName, cluster, refFactory,newMethodToTest);
				if(op != null) return op;
			} catch(Throwable e) {
				System.err.println( "Operation.recommendedGenerate: Cannot create a random element: " + e.getMessage());
			}
		}

	}



		public static Operation invokeMethod(String cName,Map<String, Class<?>> cluster,Map<String, String[]>  refFactory,Map<String, MethodDeclared[]> methodsToTest){
		// if there are no available methods
		MethodDeclared[] usableMethods = methodsToTest.get(cName.toString());
		if(usableMethods.length <= 0) return null;
		MethodDeclared  m =usableMethods[ValuesGenerator.getRandomInt(usableMethods.length)].clone();

		
//		System.out.println("Operation.invokeMethod methodName:"+m.getName());
		
		String t = m.isStatic() ? null : getReference(cName, refFactory);

		Class<?>[] paramsType = m.getParameterTypes();
		String[] p = new String[paramsType.length];
		for(int i = 0; i < p.length; i++){
			if(Filter.isPrimitiveOrStringType(cluster.get( paramsType[i].getName().toString())))
				p[i] = 	ValuesGenerator.buildValue(paramsType[i].getName());
			else
				p[i] = generateRef(m,paramsType[i],  refFactory,cluster);
		}
		String r = null;
		Class<?> retClazz = m.getReturnType();
		if(retClazz != null) r = getReference(retClazz.getName(),refFactory);

		return new Invoke(r, t, m, p);
		
	}
	public static String generateRef(MethodDeclared  m,Class<?> clz1,Map<String, String[]>  refFactory,Map<String, Class<?>> cluster) {
		String cName = clz1.getName();
			Class<?> clz2;
//			if(cName.equals("java.lang.Object"))
//				System.out.print("");
			
	//		System.out.println("Operation.generateRef clz1:"+ cName);
			List<String> keys = new ArrayList<String>(refFactory.keySet());
			do{
				int cutRand = ValuesGenerator.getRandomInt(refFactory.size());

				cName = (String) keys.get(cutRand);

				 clz2=cluster.get(cName);
				 
				}while(!CanBeUsedAs.canBeUsedAs(clz2, clz1));
			
			m.setReplacementObjectParam(cluster.get(cName));
			if (Filter.isPrimitiveOrStringType(cluster.get(cName)))
				return (ValuesGenerator.buildValue(cName));
			if  (ValuesGenerator.getBoleean(Operation.SET_TO_NULL))
				return null;


		return getReference(cName, refFactory);
	}
	public static String getReference(String cName,Map<String, String[]>  refFactory) {
		String refs[] = refFactory.get(cName);
		
		if(refs == null) return null;

		return refs[ValuesGenerator.getRandomInt(refs.length)];
	}
}